Library Statements

library(ISLR)
library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels) 
library(stringr)
library(splitstackshape)
library(lubridate)
library(rpart.plot)
library(cluster)
library(forcats)
tidymodels_prefer()
library(probably) #install.packages('probably')
library(vip)

Dataset

imdb_top_1000 <- read_csv("~/Desktop/Statistical Machine Learning/R Files/Final Project/imdb_top_1000_CLEAN.csv")

Data Cleaning

imdb_clean <- imdb_top_1000 %>%
  cSplit("Genre", sep = ",", direction = "wide") %>%
  mutate(Gross = log(Revenue-Budget))

runtime_clean <- imdb_top_1000$Runtime %>%
  str_replace(" min", "") %>%
  as.numeric()

imdb_clean$Runtime <- runtime_clean

imdb_clean <- imdb_clean %>%
  filter(Gross != "-Inf") %>%
  drop_na(Gross, Budget)

Data

head(imdb_clean)

Regression Models

Ordinary Linear Regression Model

Creation of CV Folds

data_cv10 <- vfold_cv(imdb_clean, v = 10)

Model Spec, Recipes, and Workflows

# Model Spec

lm_spec <-
    linear_reg() %>% 
    set_engine(engine = 'lm') %>% 
    set_mode('regression')

# Recipe

full_lm_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>% 
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross, Runtime, IMDB_Rating, Meta_score, No_of_Votes)

# Workflow

full_lm_wf <- workflow() %>%
    add_recipe(full_lm_rec) %>%
    add_model(lm_spec)

Fit Full Model

full_lm_model <- fit(full_lm_wf, data = imdb_clean) 

full_lm_model %>% tidy()

Obtain Evaluation Metrics for Full Model

full_lm_modelcv <- fit_resamples(full_lm_wf, resamples = data_cv10, metrics = metric_set(rmse, rsq, mae))

full_lm_modelcv %>%
  collect_metrics()

Perform LASSO for Subset Selection (Regression Model)

Model Spec, Recipes, and Workflow

# Lasso Model Spec with tune

lm_lasso_spec_tune <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>%   # mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>%             
  set_mode('regression') 

# Recipe

data_rec_lasso <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>%                # removes variables with the same value (don't want duplicates)
    step_novel(all_nominal_predictors()) %>%      # important if you have rare categorical variables 
    step_normalize(all_numeric_predictors()) %>%  # standardization important step for LASSO
    step_dummy(all_nominal_predictors()) %>%      # creates indicator variables for categorical variables
    step_naomit(Gross, Runtime, IMDB_Rating,      # omit any NA values
                Meta_score, No_of_Votes)                            

# Workflow

lasso_wf_tune <- workflow() %>% 
  add_recipe(data_rec_lasso) %>%
  add_model(lm_lasso_spec_tune) 

Tune Model and Cross Validation

# Tune Model (trying a variety of values of Lambda penalty)

penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)),
  levels = 30)

tune_res <- tune_grid(
  lasso_wf_tune, 
  resamples = data_cv10, 
  metrics = metric_set(rmse, mae),
  grid = penalty_grid 
)

# Visualize Model Evaluation Metrics from Tuning

autoplot(tune_res) + theme_classic()

# Collect CV Metrics and Select Best Model

# Summarize Model Evaluation Metrics (CV)
lasso_mod <- collect_metrics(tune_res) %>%
  filter(.metric == 'rmse') %>%
  select(penalty, rmse = mean) 

# Choose penalty value
best_penalty <- select_best(tune_res, metric = 'rmse')

lasso_mod

Fit Final LASSO Model

# Fit Final Model

final_wf <- finalize_workflow(lasso_wf_tune, best_penalty) # incorporates penalty value to workflow

final_fit <- fit(final_wf, data = imdb_clean)

lasso_fit <- fit_resamples(final_wf, resamples = data_cv10, metrics = metric_set(rmse, rsq, mae))

tidy(final_fit)
# Final ("best") model predictors and coefficients

final_fit %>% tidy() %>% filter(estimate != 0)

Obtain Evaluation Metrics for Lasso Model

lasso_fit %>%
  collect_metrics()

Visualize Residuals for LASSO Model

lasso_mod_out <- final_fit %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

ggplot(lasso_mod_out, aes(x = .pred, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = Runtime, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = IMDB_Rating, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(lasso_mod_out, aes(x = No_of_Votes, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

GAM with Splines (TidyModels)

Build the GAM

# Build the GAM

gam_spec <- 
  gen_additive_mod() %>%
  set_engine(engine = 'mgcv') %>%
  set_mode('regression') 

gam_mod1 <- fit(gam_spec,
    Gross ~ s(Runtime, k = 20) + s(IMDB_Rating) + Meta_score + s(No_of_Votes) + Genre_1,
    data = imdb_clean 
)

Run Diagnostics

# Diagnostics: Check to see if the number of knots is large enough (if p-value is low, increase number of knots)

gam_mod1 %>% pluck('fit') %>% mgcv::gam.check()

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 9 iterations.
## The RMS GCV score gradient at convergence was 3.345103e-06 .
## The Hessian was positive definite.
## Model rank =  51 / 51 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                   k'   edf k-index p-value  
## s(Runtime)     19.00 14.98    0.97    0.26  
## s(IMDB_Rating)  9.00  4.25    0.94    0.07 .
## s(No_of_Votes)  9.00  3.99    1.06    0.91  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Diagnostics: Check to see if the number of knots is large enough

gam_mod1 %>% pluck('fit') %>% summary() 
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## Gross ~ s(Runtime, k = 20) + s(IMDB_Rating) + Meta_score + s(No_of_Votes) + 
##     Genre_1
## 
## Parametric coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      16.832784   0.429340  39.206  < 2e-16 ***
## Meta_score        0.010241   0.005299   1.932  0.05387 .  
## Genre_1Adventure -0.044096   0.247036  -0.179  0.85840    
## Genre_1Animation  1.346910   0.301529   4.467 9.81e-06 ***
## Genre_1Biography  0.223135   0.234887   0.950  0.34258    
## Genre_1Comedy     0.267256   0.227633   1.174  0.24093    
## Genre_1Crime     -0.581492   0.232807  -2.498  0.01282 *  
## Genre_1Drama     -0.334477   0.190120  -1.759  0.07914 .  
## Genre_1Family    -0.175638   0.962267  -0.183  0.85524    
## Genre_1Film-Noir -2.558791   0.976808  -2.620  0.00907 ** 
## Genre_1Horror     0.512855   0.502375   1.021  0.30781    
## Genre_1Mystery   -1.175425   0.571692  -2.056  0.04029 *  
## Genre_1Thriller  -0.520050   1.354114  -0.384  0.70110    
## Genre_1Western   -0.455014   0.817789  -0.556  0.57819    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df      F p-value    
## s(Runtime)     14.979 17.114  5.202  <2e-16 ***
## s(IMDB_Rating)  4.254  5.211 19.128  <2e-16 ***
## s(No_of_Votes)  3.989  4.958 68.466  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.521   Deviance explained = 55.4%
## GCV = 1.9035  Scale est. = 1.7723    n = 540

Visualization for Non-Linear Functions

# Visualize: Look at the estimated non-linear functions

gam_mod1 %>% pluck('fit') %>% plot()

Obtain Evaluation Metrics for GAM1

gam1_output <- gam_mod1%>% 
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
gam1_output %>%
    rsq(truth = Gross, estimate = .pred)

GAM with Splines (Recipe)

Build the GAM

spline_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>% 
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross) %>%
    step_ns(Runtime, deg_free = 19) %>%
    step_ns(No_of_Votes, deg_free = 9) %>%
    step_ns(IMDB_Rating, deg_free = 9)


spline_rec %>% prep(imdb_clean) %>% juice()
# Build the GAM

lm_spec_gam <-
  linear_reg() %>%
  set_engine(engine = 'lm') %>%
  set_mode('regression')

spline_wf <- workflow() %>%
    add_model(lm_spec) %>%
    add_recipe(spline_rec)

cv_output_spline2 <- fit_resamples( 
  spline_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(mae,rmse,rsq)
)

Obtain Evaluation Metrics for GAM2

cv_output_spline2 %>% collect_metrics()

Compare GAMs

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
cv_output_spline2 %>% collect_metrics()

The GAM created using TidyModels performs better than the recipe and GAMs. Likely has to do with the degrees of freedom of the splines.

Visualize Residuals for Final GAM (GAM1)

# Visualize Residuals
gam1_output <- new_mod %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)
## Error in predict(., new_data = imdb_clean): object 'new_mod' not found
ggplot(gam1_output, aes(x = .pred, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = Runtime, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = IMDB_Rating, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

ggplot(gam1_output, aes(x = No_of_Votes, y = resid)) +
    geom_point() +
    geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + 
    theme_classic()

There does not appear to be any significant bias after analyzing the residuals.

Compare All Regression Model Performance

full_lm_modelcv %>% collect_metrics() #OLS
lasso_fit %>% collect_metrics() #LASSO
gam1_output %>%
    rmse(truth = Gross, estimate = .pred) #GAM

The GAM with Splines obtained using TidyModels performs the best with the lowest RMSE value (1.3 compared to 1.4 for the others).

Classification Models

Create New Dataset for Classification

imdb_clean_class <- imdb_clean %>%
  mutate(success_ratio = Revenue/Budget) %>%
  mutate(flop = as.factor(ifelse(success_ratio > 2, 'FALSE', 'TRUE'))) %>%
  drop_na(flop, No_of_Votes,Runtime, IMDB_Rating,Meta_score,Genre_1)

Random Forests Model

Model Spec, Recipe, and Workflow

# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(trees = 1000, # Number of trees
           min_n = NULL,
           probability = FALSE,
           importance = 'impurity') %>%
  set_mode('classification')

# Recipe
data_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Meta_score + Genre_1,
                   data = imdb_clean_class) %>%
  step_naomit(flop, No_of_Votes, Runtime, IMDB_Rating, Meta_score, Genre_1)

# Create Workflows
data_wf_mtry3 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 3)) %>%
  add_recipe(data_rec) 

data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec) 

data_wf_mtry5 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 5)) %>%
  add_recipe(data_rec)

Fit Models with Various Values for mtry

# Fit Models
set.seed(123) # make sure to run this before each fit so that you have the same 1000 trees
data_fit_mtry3 <- fit(data_wf_mtry3, data = imdb_clean_class)

set.seed(123) 
data_fit_mtry4 <- fit(data_wf_mtry4, data = imdb_clean_class)

set.seed(123)
data_fit_mtry5 <- fit(data_wf_mtry5, data = imdb_clean_class)

Obtain OOB Predictions and Evaluation Metrics to Choose mtry Value

# Custom Function to get OOB predictions, true observed outcomes and add a user-provided model label
rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_class = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          flop = truth,
          model = model_label
      )
}
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(
    rf_OOB_output(data_fit_mtry3,3, imdb_clean_class %>% pull(flop)),
    rf_OOB_output(data_fit_mtry4,4, imdb_clean_class %>% pull(flop)),
    rf_OOB_output(data_fit_mtry5,5, imdb_clean_class %>% pull(flop))
)


data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = flop, estimate = .pred_class)
data_rf_OOB_output %>% 
  group_by(model) %>%
  accuracy(truth = flop, estimate = .pred_class) %>%
  mutate(mtry = as.numeric(stringr::str_replace(model,'mtry',''))) %>%
  ggplot(aes(x = mtry, y = .estimate )) + 
  geom_point() +
  geom_line() +
  theme_classic()

Evaluating Selected Model - Confusion Matrix

rf_OOB_output(data_fit_mtry4,4, imdb_clean_class %>% pull(flop)) %>%
    conf_mat(truth = flop, estimate= .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   468   65
##      TRUE      7    0

Variable Importance

Impurity

# Impurity

model_output <-data_fit_mtry4 %>% 
    extract_fit_engine() 

model_output %>% 
    vip(num_features = 10) + theme_classic() #based on impurity, 10 meaning the top 10

model_output %>% vip::vi() %>% head()
model_output %>% vip::vi() %>% tail()

Permuation

# Permutation

model_output2 <- data_wf_mtry4 %>% 
  update_model(rf_spec %>% set_args(importance = "permutation")) %>% #based on permutation
  fit(data = imdb_clean_class) %>% 
    extract_fit_engine() 

model_output2 %>% 
    vip(num_features = 10) + theme_classic()

model_output2 %>% vip::vi() %>% head()
model_output2 %>% vip::vi() %>% tail()

Violin Graphs

ggplot(imdb_clean_class, aes(x = flop, y = No_of_Votes)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Runtime)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = IMDB_Rating)) +
    geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Meta_score)) +
    geom_violin() + theme_classic()

Logistic Regression

Model Spec, Recipe, and Workflow

set.seed(123)

# Logistic Regression Model Spec
logistic_spec <- logistic_reg() %>%
    set_engine('glm') %>%
    set_mode('classification')

# Recipe
logistic_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Genre_1,
                   data = imdb_clean_class)

# Workflow (Recipe + Model) for Full Log Model
log_wf <- workflow() %>%
    add_recipe(logistic_rec) %>%
    add_model(logistic_spec)

Fit Model

# Fit Model
log_fit <- fit(log_wf, data = imdb_clean_class)

tidy(log_fit)

Add Variable for Odds Ratio

log_fit %>% tidy() %>%
  mutate(OR = exp(estimate))

Cross Validation and Evaluation Metrics

# Creation of CV Folds
data_cv10_class <- vfold_cv(imdb_clean_class, v = 10)
log_modelcv <- fit_resamples(log_wf, resamples = data_cv10_class, metrics = metric_set(accuracy,sens,yardstick::spec))

log_modelcv %>%
  collect_metrics()

Picking Threshold

Boxplots

final_output <- log_fit %>% predict(new_data = imdb_clean_class, type='prob') %>% bind_cols(imdb_clean_class)

final_output %>%
  ggplot(aes(x = flop, y = .pred_TRUE)) +
  geom_boxplot()

ROC Curve

# Use soft predictions
final_output %>%
    roc_curve(flop,.pred_TRUE,event_level = 'second') %>%
    autoplot()

J Index vs. Threshold

# Thresholds in terms of reference level
threshold_output <- final_output %>%
    threshold_perf(truth = flop, estimate = .pred_FALSE, thresholds = seq(0,1,by=.01)) 

# J-index v. Threshold for no flop
threshold_output %>%
    filter(.metric == 'j_index') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'J-index', x = 'threshold') +
    theme_classic()

J Index vs. Distance

threshold_output %>%
    filter(.metric == 'j_index') %>%
    arrange(desc(.estimate))
# Distance vs. Threshold

threshold_output %>%
    filter(.metric == 'distance') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'Distance', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'distance') %>%
    arrange(.estimate)

Obtain Evaluation Metrics for Logistic Regression

log_metrics <- metric_set(accuracy,sens,yardstick::spec)

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = .12)) %>%
    log_metrics(truth = flop, estimate = .pred_class, event_level = 'second')
final_output %>%
  mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = .12)) %>%
  conf_mat(truth = flop, estimate = .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   475   65
##      TRUE      0    0

Predictions

predict(log_fit, new_data = data.frame(No_of_Votes = 10000, Runtime = 112, IMDB_Rating = 9.8,
                                        Genre_1 = "Drama"), type = "prob"
)

We manually performed the hard predictions.

Unsupervised Learning - Clustering

K-Means Clustering

Preliminary Visualizations

ggplot(imdb_clean, aes(x = Budget, y = Runtime)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = Budget, y = Gross)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = No_of_Votes, y = Runtime)) + 
  geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = Gross, y = No_of_Votes)) + 
  geom_point() + theme_classic()

Create Clusters

imdb_sub <- imdb_clean %>%
    select(Budget, Runtime)

set.seed(253)

Determine Number of Clusters

# Data-specific function to cluster and calculate total within-cluster SS
imdb_cluster_ss <- function(k){
    # Perform clustering
    kclust <- kmeans(scale(imdb_sub), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(
    k = 1:15,
    tot_wc_ss = purrr::map_dbl(1:15, imdb_cluster_ss)
) %>% 
    ggplot(aes(x = k, y = tot_wc_ss)) +
    geom_point() + 
    labs(x = "Number of clusters",y = 'Total within-cluster sum of squares') + 
    theme_classic()

Select k = 8 Clusters

kclust_k8 <- kmeans(scale(imdb_sub), centers = 8)

kclust_k8$cluster   # Display cluster assignments
##   [1] 1 5 8 2 4 2 5 2 8 6 6 6 5 2 6 1 8 1 6 2 7 1 7 1 1 7 1 6 5 6 1 7 7 4 6 5 7
##  [38] 7 7 4 6 8 5 5 8 7 7 5 5 7 4 7 7 1 7 1 6 7 7 7 1 1 4 2 4 7 1 5 5 5 7 1 1 5
##  [75] 1 2 1 1 1 7 4 7 7 4 5 2 1 6 8 6 5 5 8 6 5 1 8 5 6 7 1 5 2 1 1 1 1 4 5 1 2
## [112] 1 7 7 1 4 7 1 5 7 6 1 1 1 4 1 5 8 7 6 4 4 8 5 1 1 1 4 2 7 3 6 5 3 3 3 7 4
## [149] 4 2 7 3 1 1 4 1 5 7 4 2 7 1 4 2 1 1 2 1 1 1 1 2 7 5 7 7 1 1 2 7 4 4 1 5 1
## [186] 7 6 8 4 7 8 1 7 7 5 3 5 1 7 7 3 6 3 1 7 8 5 1 1 6 8 3 3 2 2 1 7 1 1 1 4 4
## [223] 7 5 4 2 4 2 1 1 4 1 7 2 1 4 1 1 7 5 1 7 2 7 1 7 7 1 5 2 4 1 5 1 4 7 7 1 4
## [260] 4 5 7 7 4 7 7 7 7 3 7 8 7 8 1 4 7 7 7 1 5 8 7 1 7 6 4 8 4 1 1 6 7 3 3 5 7
## [297] 5 7 7 1 7 4 4 1 4 1 1 1 5 4 7 7 6 2 2 7 4 7 4 7 7 7 7 1 3 1 8 5 8 1 1 1 4
## [334] 8 1 6 6 5 8 5 7 4 4 4 8 1 1 7 6 8 1 7 1 8 2 1 1 1 1 1 4 7 7 6 3 8 1 5 1 7
## [371] 1 1 4 7 7 1 1 3 1 7 4 7 7 5 4 1 7 7 7 1 7 7 7 7 7 7 5 1 2 1 7 4 4 7 7 7 4
## [408] 6 1 7 8 5 7 8 4 4 3 3 8 1 1 7 8 1 3 4 8 6 7 7 1 6 7 8 5 1 3 7 5 7 8 6 3 8
## [445] 7 1 7 6 3 7 7 4 3 4 7 6 6 1 1 4 2 1 4 2 3 1 1 3 5 1 4 4 1 7 4 4 4 4 4 1 7
## [482] 7 7 7 5 7 5 1 1 7 7 7 1 7 1 7 1 8 7 1 1 1 7 4 8 7 7 7 3 7 7 7 1 4 7 6 1 7
## [519] 3 3 6 1 7 3 8 3 1 6 7 7 4 1 7 7 7 7 3 6 6 6 7 4 5 3 7 5 1 6 1 6 7 7 1 7 5
## [556] 4 4 4 7 7 4 4 4 7 4 1 1 5 4 7 2 7
imdb_clean <- imdb_clean %>%
    mutate(kclust_8 = factor(kclust_k8$cluster))

Visualize Cluster Assignments

# Visualize the cluster assignments on the original scatterplot
imdb_clean %>%
  ggplot(aes(x = Budget, y = Runtime, color = kclust_8)) +
    geom_point() + theme_classic()

Interpreting Clusters

Exploring Genre Breakdown

# Count of Movies per Genre (Primary Genre)
imdb_clean %>%
  count(Genre_1)
# Count of Movies per Genre (Secondary Genre)
imdb_clean %>%
  count(Genre_2)
# Count of Movies per Genre (Overall Genre)
imdb_clean %>%
  count(New_Genre)
# Genres vs Cluster

# How many of each Genre 1 in each cluster
imdb_clean %>%
  group_by(kclust_8) %>%
  count(Genre_1)
# How many of each Genre 2 in each cluster
imdb_clean %>%
  group_by(kclust_8) %>%
  count(Genre_2)
# How many movies in each cluster
imdb_clean%>%
  count(kclust_8)

Visualizations of Genres in Each Cluster

# Genre 1
imdb_clean %>%
  ggplot(aes(x = kclust_8, fill = Genre_1)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

# Genre 2
imdb_clean %>%
  ggplot(aes(x = kclust_8, fill = Genre_2)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

# Overall Genre
imdb_clean %>%
  ggplot(aes(x = kclust_8, fill = New_Genre)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

Hierarchial Clustering

Set up Clustering and Distance Matrix

# Random subsample of 25 Movies
set.seed(253)

imdb_hc <- imdb_clean %>%
    slice_sample(n = 25)

# Select the variables to be used in clustering
imdb_hc_sub <- imdb_hc %>%
    select(Gross, Budget)

imdb_hc_full <- imdb_clean %>%
  select(Gross, Budget)

# Summary statistics for the variables
summary(imdb_hc_sub)
##      Gross           Budget         
##  Min.   :13.82   Min.   :        0  
##  1st Qu.:15.98   1st Qu.:  3000000  
##  Median :17.54   Median : 18500000  
##  Mean   :17.34   Mean   : 39318800  
##  3rd Qu.:18.88   3rd Qu.: 60000000  
##  Max.   :20.31   Max.   :190000000
# Compute a distance matrix on the scaled data
dist_mat_scaled <- dist(scale(imdb_hc_sub))     # Subset Distance Matrix

dist_mat_full <- dist(scale(imdb_hc_full))      # Full Data Distance Matrix

Create Clusters

imdb_hc_avg <- hclust(dist_mat_scaled, method = "average")    # Subset
imdb_full_avg <- hclust(dist_mat_full, method = "average")    # Full Data

Visualize Dendrograms

# Plot dendrogram on Subset
plot(imdb_hc_avg)

# Adding Genre Labels

plot(imdb_hc_avg, labels = imdb_hc$Genre_1)

plot(imdb_hc_avg, labels = paste(imdb_hc$Genre_1, imdb_hc$Genre_2))

plot(imdb_hc_avg, labels = paste(imdb_hc$Genre_1, imdb_hc$Genre_2, imdb_hc$Genre_3))

plot(imdb_hc_avg, labels = paste(imdb_hc$New_Genre))

Cutting the Tree

imdb_clean <- imdb_clean %>%
    mutate(
        hclust_num = factor(cutree(imdb_full_avg, k = 3)) # Cut into 6 clusters (k)
    )

Visualizing Genres in Final Clusters (Full Data)

ggplot(imdb_clean, aes(x = hclust_num, fill = Genre_1)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

ggplot(imdb_clean, aes(x = hclust_num, fill = New_Genre)) +
    geom_bar(position = "fill") +
    labs(x = "Cluster") + 
    theme_classic()

ggplot(imdb_clean, aes(x = Budget, y = Gross, color = hclust_num)) +
    geom_point() +
    theme_classic()

LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCBDb2RlIEFMTCIKYXV0aG9yOiAiRW1pbHksIEp1bGlhbiwgSmFjb2IsIGFuZCBBcmlzdG8iCmRhdGU6ICcyMDIyLTExLTIwJwpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IHBhcGVyCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3I9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgojIExpYnJhcnkgU3RhdGVtZW50cyAKCmBgYHtyfQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWR5bW9kZWxzKSAKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHNwbGl0c3RhY2tzaGFwZSkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZvcmNhdHMpCnRpZHltb2RlbHNfcHJlZmVyKCkKbGlicmFyeShwcm9iYWJseSkgI2luc3RhbGwucGFja2FnZXMoJ3Byb2JhYmx5JykKbGlicmFyeSh2aXApCmBgYAoKIyBEYXRhc2V0CgpgYGB7cn0KaW1kYl90b3BfMTAwMCA8LSByZWFkX2Nzdigifi9EZXNrdG9wL1N0YXRpc3RpY2FsIE1hY2hpbmUgTGVhcm5pbmcvUiBGaWxlcy9GaW5hbCBQcm9qZWN0L2ltZGJfdG9wXzEwMDBfQ0xFQU4uY3N2IikKYGBgCgojIyBEYXRhIENsZWFuaW5nCgpgYGB7cn0KaW1kYl9jbGVhbiA8LSBpbWRiX3RvcF8xMDAwICU+JQogIGNTcGxpdCgiR2VucmUiLCBzZXAgPSAiLCIsIGRpcmVjdGlvbiA9ICJ3aWRlIikgJT4lCiAgbXV0YXRlKEdyb3NzID0gbG9nKFJldmVudWUtQnVkZ2V0KSkKCnJ1bnRpbWVfY2xlYW4gPC0gaW1kYl90b3BfMTAwMCRSdW50aW1lICU+JQogIHN0cl9yZXBsYWNlKCIgbWluIiwgIiIpICU+JQogIGFzLm51bWVyaWMoKQoKaW1kYl9jbGVhbiRSdW50aW1lIDwtIHJ1bnRpbWVfY2xlYW4KCmltZGJfY2xlYW4gPC0gaW1kYl9jbGVhbiAlPiUKICBmaWx0ZXIoR3Jvc3MgIT0gIi1JbmYiKSAlPiUKICBkcm9wX25hKEdyb3NzLCBCdWRnZXQpCmBgYAoKIyMgRGF0YQoKYGBge3J9CmhlYWQoaW1kYl9jbGVhbikKYGBgCgojIFJlZ3Jlc3Npb24gTW9kZWxzCgojIyBPcmRpbmFyeSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbAoKIyMjIENyZWF0aW9uIG9mIENWIEZvbGRzCgpgYGB7cn0KZGF0YV9jdjEwIDwtIHZmb2xkX2N2KGltZGJfY2xlYW4sIHYgPSAxMCkKYGBgCgojIyMgTW9kZWwgU3BlYywgUmVjaXBlcywgYW5kIFdvcmtmbG93cwoKYGBge3J9CiMgTW9kZWwgU3BlYwoKbG1fc3BlYyA8LQogICAgbGluZWFyX3JlZygpICU+JSAKICAgIHNldF9lbmdpbmUoZW5naW5lID0gJ2xtJykgJT4lIAogICAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQoKIyBSZWNpcGUKCmZ1bGxfbG1fcmVjIDwtIHJlY2lwZShHcm9zcyB+IFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyAKICAgICAgICAgICAgICAgICAgIE5vX29mX1ZvdGVzICsgR2VucmVfMSwgZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lIAogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgCiAgICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lCiAgICBzdGVwX25hb21pdChHcm9zcywgUnVudGltZSwgSU1EQl9SYXRpbmcsIE1ldGFfc2NvcmUsIE5vX29mX1ZvdGVzKQoKIyBXb3JrZmxvdwoKZnVsbF9sbV93ZiA8LSB3b3JrZmxvdygpICU+JQogICAgYWRkX3JlY2lwZShmdWxsX2xtX3JlYykgJT4lCiAgICBhZGRfbW9kZWwobG1fc3BlYykKYGBgCgojIyMgRml0IEZ1bGwgTW9kZWwKCmBgYHtyfQpmdWxsX2xtX21vZGVsIDwtIGZpdChmdWxsX2xtX3dmLCBkYXRhID0gaW1kYl9jbGVhbikgCgpmdWxsX2xtX21vZGVsICU+JSB0aWR5KCkKYGBgCgojIyMgT2J0YWluIEV2YWx1YXRpb24gTWV0cmljcyBmb3IgRnVsbCBNb2RlbAoKYGBge3J9CmZ1bGxfbG1fbW9kZWxjdiA8LSBmaXRfcmVzYW1wbGVzKGZ1bGxfbG1fd2YsIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSwgcnNxLCBtYWUpKQoKZnVsbF9sbV9tb2RlbGN2ICU+JQogIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKIyMgUGVyZm9ybSBMQVNTTyBmb3IgU3Vic2V0IFNlbGVjdGlvbiAoUmVncmVzc2lvbiBNb2RlbCkKCiMjIyBNb2RlbCBTcGVjLCBSZWNpcGVzLCBhbmQgV29ya2Zsb3cKCmBgYHtyfQojIExhc3NvIE1vZGVsIFNwZWMgd2l0aCB0dW5lCgpsbV9sYXNzb19zcGVjX3R1bmUgPC0gCiAgbGluZWFyX3JlZygpICU+JQogIHNldF9hcmdzKG1peHR1cmUgPSAxLCBwZW5hbHR5ID0gdHVuZSgpKSAlPiUgICAjIG1peHR1cmUgPSAxIGluZGljYXRlcyBMYXNzbwogIHNldF9lbmdpbmUoZW5naW5lID0gJ2dsbW5ldCcpICU+JSAgICAgICAgICAgICAKICBzZXRfbW9kZSgncmVncmVzc2lvbicpIAoKIyBSZWNpcGUKCmRhdGFfcmVjX2xhc3NvIDwtIHJlY2lwZShHcm9zcyB+IFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyAKICAgICAgICAgICAgICAgICAgIE5vX29mX1ZvdGVzICsgR2VucmVfMSwgZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lICAgICAgICAgICAgICAgICMgcmVtb3ZlcyB2YXJpYWJsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSAoZG9uJ3Qgd2FudCBkdXBsaWNhdGVzKQogICAgc3RlcF9ub3ZlbChhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAgICAgICMgaW1wb3J0YW50IGlmIHlvdSBoYXZlIHJhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIAogICAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSAlPiUgICMgc3RhbmRhcmRpemF0aW9uIGltcG9ydGFudCBzdGVwIGZvciBMQVNTTwogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSAgICAgICMgY3JlYXRlcyBpbmRpY2F0b3IgdmFyaWFibGVzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKICAgIHN0ZXBfbmFvbWl0KEdyb3NzLCBSdW50aW1lLCBJTURCX1JhdGluZywgICAgICAjIG9taXQgYW55IE5BIHZhbHVlcwogICAgICAgICAgICAgICAgTWV0YV9zY29yZSwgTm9fb2ZfVm90ZXMpICAgICAgICAgICAgICAgICAgICAgICAgICAgIAoKIyBXb3JrZmxvdwoKbGFzc29fd2ZfdHVuZSA8LSB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKGRhdGFfcmVjX2xhc3NvKSAlPiUKICBhZGRfbW9kZWwobG1fbGFzc29fc3BlY190dW5lKSAKYGBgCgojIyMgVHVuZSBNb2RlbCBhbmQgQ3Jvc3MgVmFsaWRhdGlvbgoKYGBge3J9CgojIFR1bmUgTW9kZWwgKHRyeWluZyBhIHZhcmlldHkgb2YgdmFsdWVzIG9mIExhbWJkYSBwZW5hbHR5KQoKcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcigKICBwZW5hbHR5KHJhbmdlID0gYygtMywgMSkpLAogIGxldmVscyA9IDMwKQoKdHVuZV9yZXMgPC0gdHVuZV9ncmlkKAogIGxhc3NvX3dmX3R1bmUsIAogIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgCiAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSwgbWFlKSwKICBncmlkID0gcGVuYWx0eV9ncmlkIAopCgojIFZpc3VhbGl6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgZnJvbSBUdW5pbmcKCmF1dG9wbG90KHR1bmVfcmVzKSArIHRoZW1lX2NsYXNzaWMoKQoKIyBDb2xsZWN0IENWIE1ldHJpY3MgYW5kIFNlbGVjdCBCZXN0IE1vZGVsCgojIFN1bW1hcml6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgKENWKQpsYXNzb19tb2QgPC0gY29sbGVjdF9tZXRyaWNzKHR1bmVfcmVzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAncm1zZScpICU+JQogIHNlbGVjdChwZW5hbHR5LCBybXNlID0gbWVhbikgCgojIENob29zZSBwZW5hbHR5IHZhbHVlCmJlc3RfcGVuYWx0eSA8LSBzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gJ3Jtc2UnKQoKbGFzc29fbW9kCmBgYAoKIyMjIEZpdCBGaW5hbCBMQVNTTyBNb2RlbAoKYGBge3J9CiMgRml0IEZpbmFsIE1vZGVsCgpmaW5hbF93ZiA8LSBmaW5hbGl6ZV93b3JrZmxvdyhsYXNzb193Zl90dW5lLCBiZXN0X3BlbmFsdHkpICMgaW5jb3Jwb3JhdGVzIHBlbmFsdHkgdmFsdWUgdG8gd29ya2Zsb3cKCmZpbmFsX2ZpdCA8LSBmaXQoZmluYWxfd2YsIGRhdGEgPSBpbWRiX2NsZWFuKQoKbGFzc29fZml0IDwtIGZpdF9yZXNhbXBsZXMoZmluYWxfd2YsIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSwgcnNxLCBtYWUpKQoKdGlkeShmaW5hbF9maXQpCgojIEZpbmFsICgiYmVzdCIpIG1vZGVsIHByZWRpY3RvcnMgYW5kIGNvZWZmaWNpZW50cwoKZmluYWxfZml0ICU+JSB0aWR5KCkgJT4lIGZpbHRlcihlc3RpbWF0ZSAhPSAwKQpgYGAKCiMjIyBPYnRhaW4gRXZhbHVhdGlvbiBNZXRyaWNzIGZvciBMYXNzbyBNb2RlbAoKYGBge3J9Cmxhc3NvX2ZpdCAlPiUKICBjb2xsZWN0X21ldHJpY3MoKQpgYGAKCiMjIyBWaXN1YWxpemUgUmVzaWR1YWxzIGZvciBMQVNTTyBNb2RlbAoKYGBge3J9Cmxhc3NvX21vZF9vdXQgPC0gZmluYWxfZml0ICU+JQogICAgcHJlZGljdChuZXdfZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgYmluZF9jb2xzKGltZGJfY2xlYW4pICU+JQogICAgbXV0YXRlKHJlc2lkID0gR3Jvc3MgLSAucHJlZCkKCmdncGxvdChsYXNzb19tb2Rfb3V0LCBhZXMoeCA9IC5wcmVkLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QobGFzc29fbW9kX291dCwgYWVzKHggPSBSdW50aW1lLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QobGFzc29fbW9kX291dCwgYWVzKHggPSBJTURCX1JhdGluZywgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGxhc3NvX21vZF9vdXQsIGFlcyh4ID0gTm9fb2ZfVm90ZXMsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyBHQU0gd2l0aCBTcGxpbmVzIChUaWR5TW9kZWxzKQoKIyMjIEJ1aWxkIHRoZSBHQU0KCmBgYHtyfQojIEJ1aWxkIHRoZSBHQU0KCmdhbV9zcGVjIDwtIAogIGdlbl9hZGRpdGl2ZV9tb2QoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdtZ2N2JykgJT4lCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKSAKCmdhbV9tb2QxIDwtIGZpdChnYW1fc3BlYywKICAgIEdyb3NzIH4gcyhSdW50aW1lLCBrID0gMjApICsgcyhJTURCX1JhdGluZykgKyBNZXRhX3Njb3JlICsgcyhOb19vZl9Wb3RlcykgKyBHZW5yZV8xLAogICAgZGF0YSA9IGltZGJfY2xlYW4gCikKCmBgYAoKIyMjIFJ1biBEaWFnbm9zdGljcwoKYGBgYHtyfQojIERpYWdub3N0aWNzOiBDaGVjayB0byBzZWUgaWYgdGhlIG51bWJlciBvZiBrbm90cyBpcyBsYXJnZSBlbm91Z2ggKGlmIHAtdmFsdWUgaXMgbG93LCBpbmNyZWFzZSBudW1iZXIgb2Yga25vdHMpCgpnYW1fbW9kMSAlPiUgcGx1Y2soJ2ZpdCcpICU+JSBtZ2N2OjpnYW0uY2hlY2soKQpgYGAKCmBgYHtyfQojIERpYWdub3N0aWNzOiBDaGVjayB0byBzZWUgaWYgdGhlIG51bWJlciBvZiBrbm90cyBpcyBsYXJnZSBlbm91Z2gKCmdhbV9tb2QxICU+JSBwbHVjaygnZml0JykgJT4lIHN1bW1hcnkoKSAKYGBgCgojIyMgVmlzdWFsaXphdGlvbiBmb3IgTm9uLUxpbmVhciBGdW5jdGlvbnMKCmBgYHtyfQojIFZpc3VhbGl6ZTogTG9vayBhdCB0aGUgZXN0aW1hdGVkIG5vbi1saW5lYXIgZnVuY3Rpb25zCgpnYW1fbW9kMSAlPiUgcGx1Y2soJ2ZpdCcpICU+JSBwbG90KCkKYGBgCgojIyMgT2J0YWluIEV2YWx1YXRpb24gTWV0cmljcyBmb3IgR0FNMQoKYGBge3J9CmdhbTFfb3V0cHV0IDwtIGdhbV9tb2QxJT4lIAogICAgcHJlZGljdChuZXdfZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgYmluZF9jb2xzKGltZGJfY2xlYW4pICU+JQogICAgbXV0YXRlKHJlc2lkID0gR3Jvc3MgLSAucHJlZCkKCmdhbTFfb3V0cHV0ICU+JQogICAgcm1zZSh0cnV0aCA9IEdyb3NzLCBlc3RpbWF0ZSA9IC5wcmVkKQoKZ2FtMV9vdXRwdXQgJT4lCiAgICByc3EodHJ1dGggPSBHcm9zcywgZXN0aW1hdGUgPSAucHJlZCkKYGBgCgojIyBHQU0gd2l0aCBTcGxpbmVzIChSZWNpcGUpCgojIyMgQnVpbGQgdGhlIEdBTQoKYGBge3J9CnNwbGluZV9yZWMgPC0gcmVjaXBlKEdyb3NzIH4gUnVudGltZSArIElNREJfUmF0aW5nICsgTWV0YV9zY29yZSArIAogICAgICAgICAgICAgICAgICAgTm9fb2ZfVm90ZXMgKyBHZW5yZV8xLCBkYXRhID0gaW1kYl9jbGVhbikgJT4lCiAgICBzdGVwX256dihhbGxfcHJlZGljdG9ycygpKSAlPiUgCiAgICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JSAKICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUKICAgIHN0ZXBfbmFvbWl0KEdyb3NzKSAlPiUKICAgIHN0ZXBfbnMoUnVudGltZSwgZGVnX2ZyZWUgPSAxOSkgJT4lCiAgICBzdGVwX25zKE5vX29mX1ZvdGVzLCBkZWdfZnJlZSA9IDkpICU+JQogICAgc3RlcF9ucyhJTURCX1JhdGluZywgZGVnX2ZyZWUgPSA5KQoKCnNwbGluZV9yZWMgJT4lIHByZXAoaW1kYl9jbGVhbikgJT4lIGp1aWNlKCkKYGBgCgpgYGB7cn0KIyBCdWlsZCB0aGUgR0FNCgpsbV9zcGVjX2dhbSA8LQogIGxpbmVhcl9yZWcoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdsbScpICU+JQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykKCnNwbGluZV93ZiA8LSB3b3JrZmxvdygpICU+JQogICAgYWRkX21vZGVsKGxtX3NwZWMpICU+JQogICAgYWRkX3JlY2lwZShzcGxpbmVfcmVjKQoKY3Zfb3V0cHV0X3NwbGluZTIgPC0gZml0X3Jlc2FtcGxlcyggCiAgc3BsaW5lX3dmLCAjIHdvcmtmbG93CiAgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCAjIGN2IGZvbGRzCiAgbWV0cmljcyA9IG1ldHJpY19zZXQobWFlLHJtc2UscnNxKQopCgpgYGAKCiMjIyBPYnRhaW4gRXZhbHVhdGlvbiBNZXRyaWNzIGZvciBHQU0yCgpgYGB7cn0KY3Zfb3V0cHV0X3NwbGluZTIgJT4lIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKIyMgQ29tcGFyZSBHQU1zCgpgYGB7cn0KZ2FtMV9vdXRwdXQgJT4lCiAgICBybXNlKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpCgpjdl9vdXRwdXRfc3BsaW5lMiAlPiUgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpUaGUgR0FNIGNyZWF0ZWQgdXNpbmcgVGlkeU1vZGVscyBwZXJmb3JtcyBiZXR0ZXIgdGhhbiB0aGUgcmVjaXBlIGFuZCBHQU1zLiBMaWtlbHkgaGFzIHRvIGRvIHdpdGggdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBvZiB0aGUgc3BsaW5lcy4KCiMjIyBWaXN1YWxpemUgUmVzaWR1YWxzIGZvciBGaW5hbCBHQU0gKEdBTTEpCgpgYGB7cn0KCiMgVmlzdWFsaXplIFJlc2lkdWFscwpnYW0xX291dHB1dCA8LSBuZXdfbW9kICU+JQogICAgcHJlZGljdChuZXdfZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgYmluZF9jb2xzKGltZGJfY2xlYW4pICU+JQogICAgbXV0YXRlKHJlc2lkID0gR3Jvc3MgLSAucHJlZCkKCgpnZ3Bsb3QoZ2FtMV9vdXRwdXQsIGFlcyh4ID0gLnByZWQsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChnYW0xX291dHB1dCwgYWVzKHggPSBSdW50aW1lLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoZ2FtMV9vdXRwdXQsIGFlcyh4ID0gSU1EQl9SYXRpbmcsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChnYW0xX291dHB1dCwgYWVzKHggPSBOb19vZl9Wb3RlcywgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQpgYGAKClRoZXJlIGRvZXMgbm90IGFwcGVhciB0byBiZSBhbnkgc2lnbmlmaWNhbnQgYmlhcyBhZnRlciBhbmFseXppbmcgdGhlIHJlc2lkdWFscy4KCiMjIENvbXBhcmUgQWxsIFJlZ3Jlc3Npb24gTW9kZWwgUGVyZm9ybWFuY2UKCmBgYHtyfQpmdWxsX2xtX21vZGVsY3YgJT4lIGNvbGxlY3RfbWV0cmljcygpICNPTFMKYGBgCgpgYGB7cn0KbGFzc29fZml0ICU+JSBjb2xsZWN0X21ldHJpY3MoKSAjTEFTU08KYGBgCgpgYGB7cn0KZ2FtMV9vdXRwdXQgJT4lCiAgICBybXNlKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpICNHQU0KYGBgCgpUaGUgR0FNIHdpdGggU3BsaW5lcyBvYnRhaW5lZCB1c2luZyBUaWR5TW9kZWxzIHBlcmZvcm1zIHRoZSBiZXN0IHdpdGggdGhlIGxvd2VzdCBSTVNFIHZhbHVlICgxLjMgY29tcGFyZWQgdG8gMS40IGZvciB0aGUgb3RoZXJzKS4KCgojIENsYXNzaWZpY2F0aW9uIE1vZGVscwoKIyMgQ3JlYXRlIE5ldyBEYXRhc2V0IGZvciBDbGFzc2lmaWNhdGlvbgoKYGBge3J9CmltZGJfY2xlYW5fY2xhc3MgPC0gaW1kYl9jbGVhbiAlPiUKICBtdXRhdGUoc3VjY2Vzc19yYXRpbyA9IFJldmVudWUvQnVkZ2V0KSAlPiUKICBtdXRhdGUoZmxvcCA9IGFzLmZhY3RvcihpZmVsc2Uoc3VjY2Vzc19yYXRpbyA+IDIsICdGQUxTRScsICdUUlVFJykpKSAlPiUKICBkcm9wX25hKGZsb3AsIE5vX29mX1ZvdGVzLFJ1bnRpbWUsIElNREJfUmF0aW5nLE1ldGFfc2NvcmUsR2VucmVfMSkKYGBgCgojIyBSYW5kb20gRm9yZXN0cyBNb2RlbAoKIyMjIE1vZGVsIFNwZWMsIFJlY2lwZSwgYW5kIFdvcmtmbG93CgpgYGB7cn0KIyBNb2RlbCBTcGVjaWZpY2F0aW9uCnJmX3NwZWMgPC0gcmFuZF9mb3Jlc3QoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdyYW5nZXInKSAlPiUgCiAgc2V0X2FyZ3ModHJlZXMgPSAxMDAwLCAjIE51bWJlciBvZiB0cmVlcwogICAgICAgICAgIG1pbl9uID0gTlVMTCwKICAgICAgICAgICBwcm9iYWJpbGl0eSA9IEZBTFNFLAogICAgICAgICAgIGltcG9ydGFuY2UgPSAnaW1wdXJpdHknKSAlPiUKICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKQoKIyBSZWNpcGUKZGF0YV9yZWMgPC0gcmVjaXBlKGZsb3AgfiBOb19vZl9Wb3RlcyArIFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyBHZW5yZV8xLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpICU+JQogIHN0ZXBfbmFvbWl0KGZsb3AsIE5vX29mX1ZvdGVzLCBSdW50aW1lLCBJTURCX1JhdGluZywgTWV0YV9zY29yZSwgR2VucmVfMSkKCiMgQ3JlYXRlIFdvcmtmbG93cwpkYXRhX3dmX210cnkzIDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSAzKSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykgCgpkYXRhX3dmX210cnk0IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSA0KSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykgCgpkYXRhX3dmX210cnk1IDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSA1KSkgJT4lCiAgYWRkX3JlY2lwZShkYXRhX3JlYykKYGBgCgojIyMgRml0IE1vZGVscyB3aXRoIFZhcmlvdXMgVmFsdWVzIGZvciBtdHJ5CgpgYGB7cn0KIyBGaXQgTW9kZWxzCnNldC5zZWVkKDEyMykgIyBtYWtlIHN1cmUgdG8gcnVuIHRoaXMgYmVmb3JlIGVhY2ggZml0IHNvIHRoYXQgeW91IGhhdmUgdGhlIHNhbWUgMTAwMCB0cmVlcwpkYXRhX2ZpdF9tdHJ5MyA8LSBmaXQoZGF0YV93Zl9tdHJ5MywgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCgpzZXQuc2VlZCgxMjMpIApkYXRhX2ZpdF9tdHJ5NCA8LSBmaXQoZGF0YV93Zl9tdHJ5NCwgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCgpzZXQuc2VlZCgxMjMpCmRhdGFfZml0X210cnk1IDwtIGZpdChkYXRhX3dmX210cnk1LCBkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykKYGBgCgojIyMgT2J0YWluIE9PQiBQcmVkaWN0aW9ucyBhbmQgRXZhbHVhdGlvbiBNZXRyaWNzIHRvIENob29zZSBtdHJ5IFZhbHVlCgpgYGB7cn0KIyBDdXN0b20gRnVuY3Rpb24gdG8gZ2V0IE9PQiBwcmVkaWN0aW9ucywgdHJ1ZSBvYnNlcnZlZCBvdXRjb21lcyBhbmQgYWRkIGEgdXNlci1wcm92aWRlZCBtb2RlbCBsYWJlbApyZl9PT0Jfb3V0cHV0IDwtIGZ1bmN0aW9uKGZpdF9tb2RlbCwgbW9kZWxfbGFiZWwsIHRydXRoKXsKICAgIHRpYmJsZSgKICAgICAgICAgIC5wcmVkX2NsYXNzID0gZml0X21vZGVsICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKSAlPiUgcGx1Y2soJ3ByZWRpY3Rpb25zJyksICNPT0IgcHJlZGljdGlvbnMKICAgICAgICAgIGZsb3AgPSB0cnV0aCwKICAgICAgICAgIG1vZGVsID0gbW9kZWxfbGFiZWwKICAgICAgKQp9CmBgYAoKCmBgYHtyfQojIEV2YWx1YXRlIE9PQiBNZXRyaWNzCgpkYXRhX3JmX09PQl9vdXRwdXQgPC0gYmluZF9yb3dzKAogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MywzLCBpbWRiX2NsZWFuX2NsYXNzICU+JSBwdWxsKGZsb3ApKSwKICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTQsNCwgaW1kYl9jbGVhbl9jbGFzcyAlPiUgcHVsbChmbG9wKSksCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk1LDUsIGltZGJfY2xlYW5fY2xhc3MgJT4lIHB1bGwoZmxvcCkpCikKCgpkYXRhX3JmX09PQl9vdXRwdXQgJT4lIAogICAgZ3JvdXBfYnkobW9kZWwpICU+JQogICAgYWNjdXJhY3kodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKZGF0YV9yZl9PT0Jfb3V0cHV0ICU+JSAKICBncm91cF9ieShtb2RlbCkgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUKICBtdXRhdGUobXRyeSA9IGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlcGxhY2UobW9kZWwsJ210cnknLCcnKSkpICU+JQogIGdncGxvdChhZXMoeCA9IG10cnksIHkgPSAuZXN0aW1hdGUgKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgRXZhbHVhdGluZyBTZWxlY3RlZCBNb2RlbCAtIENvbmZ1c2lvbiBNYXRyaXgKCmBgYHtyfQpyZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk0LDQsIGltZGJfY2xlYW5fY2xhc3MgJT4lIHB1bGwoZmxvcCkpICU+JQogICAgY29uZl9tYXQodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZT0gLnByZWRfY2xhc3MpCmBgYAoKIyMjIFZhcmlhYmxlIEltcG9ydGFuY2UKCiMjIyMgSW1wdXJpdHkKCmBgYHtyfQojIEltcHVyaXR5Cgptb2RlbF9vdXRwdXQgPC1kYXRhX2ZpdF9tdHJ5NCAlPiUgCiAgICBleHRyYWN0X2ZpdF9lbmdpbmUoKSAKCm1vZGVsX291dHB1dCAlPiUgCiAgICB2aXAobnVtX2ZlYXR1cmVzID0gMTApICsgdGhlbWVfY2xhc3NpYygpICNiYXNlZCBvbiBpbXB1cml0eSwgMTAgbWVhbmluZyB0aGUgdG9wIDEwCgptb2RlbF9vdXRwdXQgJT4lIHZpcDo6dmkoKSAlPiUgaGVhZCgpCm1vZGVsX291dHB1dCAlPiUgdmlwOjp2aSgpICU+JSB0YWlsKCkKYGBgCgojIyMjIFBlcm11YXRpb24KCmBgYHtyfQojIFBlcm11dGF0aW9uCgptb2RlbF9vdXRwdXQyIDwtIGRhdGFfd2ZfbXRyeTQgJT4lIAogIHVwZGF0ZV9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhpbXBvcnRhbmNlID0gInBlcm11dGF0aW9uIikpICU+JSAjYmFzZWQgb24gcGVybXV0YXRpb24KICBmaXQoZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpICU+JSAKICAgIGV4dHJhY3RfZml0X2VuZ2luZSgpIAoKbW9kZWxfb3V0cHV0MiAlPiUgCiAgICB2aXAobnVtX2ZlYXR1cmVzID0gMTApICsgdGhlbWVfY2xhc3NpYygpCgoKbW9kZWxfb3V0cHV0MiAlPiUgdmlwOjp2aSgpICU+JSBoZWFkKCkKbW9kZWxfb3V0cHV0MiAlPiUgdmlwOjp2aSgpICU+JSB0YWlsKCkKYGBgCgojIyMgVmlvbGluIEdyYXBocwoKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuX2NsYXNzLCBhZXMoeCA9IGZsb3AsIHkgPSBOb19vZl9Wb3RlcykpICsKICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuX2NsYXNzLCBhZXMoeCA9IGZsb3AsIHkgPSBSdW50aW1lKSkgKwogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IElNREJfUmF0aW5nKSkgKwogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IE1ldGFfc2NvcmUpKSArCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMgTG9naXN0aWMgUmVncmVzc2lvbgoKIyMjIE1vZGVsIFNwZWMsIFJlY2lwZSwgYW5kIFdvcmtmbG93CgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQoKIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIFNwZWMKbG9naXN0aWNfc3BlYyA8LSBsb2dpc3RpY19yZWcoKSAlPiUKICAgIHNldF9lbmdpbmUoJ2dsbScpICU+JQogICAgc2V0X21vZGUoJ2NsYXNzaWZpY2F0aW9uJykKCiMgUmVjaXBlCmxvZ2lzdGljX3JlYyA8LSByZWNpcGUoZmxvcCB+IE5vX29mX1ZvdGVzICsgUnVudGltZSArIElNREJfUmF0aW5nICsgR2VucmVfMSwKICAgICAgICAgICAgICAgICAgIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKQoKIyBXb3JrZmxvdyAoUmVjaXBlICsgTW9kZWwpIGZvciBGdWxsIExvZyBNb2RlbApsb2dfd2YgPC0gd29ya2Zsb3coKSAlPiUKICAgIGFkZF9yZWNpcGUobG9naXN0aWNfcmVjKSAlPiUKICAgIGFkZF9tb2RlbChsb2dpc3RpY19zcGVjKQpgYGAKCiMjIyBGaXQgTW9kZWwKCmBgYHtyfQoKIyBGaXQgTW9kZWwKbG9nX2ZpdCA8LSBmaXQobG9nX3dmLCBkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykKCnRpZHkobG9nX2ZpdCkKYGBgCgojIyMgQWRkIFZhcmlhYmxlIGZvciBPZGRzIFJhdGlvCgpgYGB7cn0KbG9nX2ZpdCAlPiUgdGlkeSgpICU+JQogIG11dGF0ZShPUiA9IGV4cChlc3RpbWF0ZSkpCmBgYAoKIyMjIENyb3NzIFZhbGlkYXRpb24gYW5kIEV2YWx1YXRpb24gTWV0cmljcwoKYGBge3J9CiMgQ3JlYXRpb24gb2YgQ1YgRm9sZHMKZGF0YV9jdjEwX2NsYXNzIDwtIHZmb2xkX2N2KGltZGJfY2xlYW5fY2xhc3MsIHYgPSAxMCkKYGBgCgpgYGB7cn0KbG9nX21vZGVsY3YgPC0gZml0X3Jlc2FtcGxlcyhsb2dfd2YsIHJlc2FtcGxlcyA9IGRhdGFfY3YxMF9jbGFzcywgbWV0cmljcyA9IG1ldHJpY19zZXQoYWNjdXJhY3ksc2Vucyx5YXJkc3RpY2s6OnNwZWMpKQoKbG9nX21vZGVsY3YgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgojIyMgUGlja2luZyBUaHJlc2hvbGQKCiMjIyMgQm94cGxvdHMKCmBgYHtyfQpmaW5hbF9vdXRwdXQgPC0gbG9nX2ZpdCAlPiUgcHJlZGljdChuZXdfZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MsIHR5cGU9J3Byb2InKSAlPiUgYmluZF9jb2xzKGltZGJfY2xlYW5fY2xhc3MpCgpmaW5hbF9vdXRwdXQgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmxvcCwgeSA9IC5wcmVkX1RSVUUpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgojIyMjIFJPQyBDdXJ2ZQoKYGBge3J9CiMgVXNlIHNvZnQgcHJlZGljdGlvbnMKZmluYWxfb3V0cHV0ICU+JQogICAgcm9jX2N1cnZlKGZsb3AsLnByZWRfVFJVRSxldmVudF9sZXZlbCA9ICdzZWNvbmQnKSAlPiUKICAgIGF1dG9wbG90KCkKYGBgCgojIyMjIEogSW5kZXggdnMuIFRocmVzaG9sZAoKYGBge3J9CiMgVGhyZXNob2xkcyBpbiB0ZXJtcyBvZiByZWZlcmVuY2UgbGV2ZWwKdGhyZXNob2xkX291dHB1dCA8LSBmaW5hbF9vdXRwdXQgJT4lCiAgICB0aHJlc2hvbGRfcGVyZih0cnV0aCA9IGZsb3AsIGVzdGltYXRlID0gLnByZWRfRkFMU0UsIHRocmVzaG9sZHMgPSBzZXEoMCwxLGJ5PS4wMSkpIAoKIyBKLWluZGV4IHYuIFRocmVzaG9sZCBmb3Igbm8gZmxvcAp0aHJlc2hvbGRfb3V0cHV0ICU+JQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2pfaW5kZXgnKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IC50aHJlc2hvbGQsIHkgPSAuZXN0aW1hdGUpKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBsYWJzKHkgPSAnSi1pbmRleCcsIHggPSAndGhyZXNob2xkJykgKwogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMjIyBKIEluZGV4IHZzLiBEaXN0YW5jZQoKYGBge3J9CnRocmVzaG9sZF9vdXRwdXQgJT4lCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnal9pbmRleCcpICU+JQogICAgYXJyYW5nZShkZXNjKC5lc3RpbWF0ZSkpCmBgYAoKYGBge3J9CiMgRGlzdGFuY2UgdnMuIFRocmVzaG9sZAoKdGhyZXNob2xkX291dHB1dCAlPiUKICAgIGZpbHRlcigubWV0cmljID09ICdkaXN0YW5jZScpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gLnRocmVzaG9sZCwgeSA9IC5lc3RpbWF0ZSkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGxhYnMoeSA9ICdEaXN0YW5jZScsIHggPSAndGhyZXNob2xkJykgKwogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CnRocmVzaG9sZF9vdXRwdXQgJT4lCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnZGlzdGFuY2UnKSAlPiUKICAgIGFycmFuZ2UoLmVzdGltYXRlKQpgYGAKCiMjIyBPYnRhaW4gRXZhbHVhdGlvbiBNZXRyaWNzIGZvciBMb2dpc3RpYyBSZWdyZXNzaW9uCgpgYGB7cn0KbG9nX21ldHJpY3MgPC0gbWV0cmljX3NldChhY2N1cmFjeSxzZW5zLHlhcmRzdGljazo6c3BlYykKCmZpbmFsX291dHB1dCAlPiUKICAgIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfRkFMU0UsIGxldmVscyhmbG9wKSwgdGhyZXNob2xkID0gLjEyKSkgJT4lCiAgICBsb2dfbWV0cmljcyh0cnV0aCA9IGZsb3AsIGVzdGltYXRlID0gLnByZWRfY2xhc3MsIGV2ZW50X2xldmVsID0gJ3NlY29uZCcpCgpmaW5hbF9vdXRwdXQgJT4lCiAgbXV0YXRlKC5wcmVkX2NsYXNzID0gbWFrZV90d29fY2xhc3NfcHJlZCgucHJlZF9GQUxTRSwgbGV2ZWxzKGZsb3ApLCB0aHJlc2hvbGQgPSAuMTIpKSAlPiUKICBjb25mX21hdCh0cnV0aCA9IGZsb3AsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKIyMjIFByZWRpY3Rpb25zCgpgYGB7cn0KcHJlZGljdChsb2dfZml0LCBuZXdfZGF0YSA9IGRhdGEuZnJhbWUoTm9fb2ZfVm90ZXMgPSAxMDAwMCwgUnVudGltZSA9IDExMiwgSU1EQl9SYXRpbmcgPSA5LjgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5yZV8xID0gIkRyYW1hIiksIHR5cGUgPSAicHJvYiIKKQpgYGAKCldlIG1hbnVhbGx5IHBlcmZvcm1lZCB0aGUgaGFyZCBwcmVkaWN0aW9ucy4KCiMgVW5zdXBlcnZpc2VkIExlYXJuaW5nIC0gQ2x1c3RlcmluZwoKIyMgSy1NZWFucyBDbHVzdGVyaW5nCgojIyMgUHJlbGltaW5hcnkgVmlzdWFsaXphdGlvbnMKCmBgYHtyfQpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBCdWRnZXQsIHkgPSBSdW50aW1lKSkgKyAKICBnZW9tX3BvaW50KCkgKyB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IEJ1ZGdldCwgeSA9IEdyb3NzKSkgKyAKICBnZW9tX3BvaW50KCkgKyB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IE5vX29mX1ZvdGVzLCB5ID0gUnVudGltZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBHcm9zcywgeSA9IE5vX29mX1ZvdGVzKSkgKyAKICBnZW9tX3BvaW50KCkgKyB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgQ3JlYXRlIENsdXN0ZXJzCgpgYGB7cn0KaW1kYl9zdWIgPC0gaW1kYl9jbGVhbiAlPiUKICAgIHNlbGVjdChCdWRnZXQsIFJ1bnRpbWUpCgpzZXQuc2VlZCgyNTMpCmBgYAoKIyMjIERldGVybWluZSBOdW1iZXIgb2YgQ2x1c3RlcnMKCmBgYHtyfQojIERhdGEtc3BlY2lmaWMgZnVuY3Rpb24gdG8gY2x1c3RlciBhbmQgY2FsY3VsYXRlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIFNTCmltZGJfY2x1c3Rlcl9zcyA8LSBmdW5jdGlvbihrKXsKICAgICMgUGVyZm9ybSBjbHVzdGVyaW5nCiAgICBrY2x1c3QgPC0ga21lYW5zKHNjYWxlKGltZGJfc3ViKSwgY2VudGVycyA9IGspCgogICAgIyBSZXR1cm4gdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICByZXR1cm4oa2NsdXN0JHRvdC53aXRoaW5zcykKfQoKdGliYmxlKAogICAgayA9IDE6MTUsCiAgICB0b3Rfd2Nfc3MgPSBwdXJycjo6bWFwX2RibCgxOjE1LCBpbWRiX2NsdXN0ZXJfc3MpCikgJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gaywgeSA9IHRvdF93Y19zcykpICsKICAgIGdlb21fcG9pbnQoKSArIAogICAgbGFicyh4ID0gIk51bWJlciBvZiBjbHVzdGVycyIseSA9ICdUb3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcycpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgU2VsZWN0IGsgPSA4IENsdXN0ZXJzCgpgYGB7cn0Ka2NsdXN0X2s4IDwtIGttZWFucyhzY2FsZShpbWRiX3N1YiksIGNlbnRlcnMgPSA4KQoKa2NsdXN0X2s4JGNsdXN0ZXIgICAjIERpc3BsYXkgY2x1c3RlciBhc3NpZ25tZW50cwoKaW1kYl9jbGVhbiA8LSBpbWRiX2NsZWFuICU+JQogICAgbXV0YXRlKGtjbHVzdF84ID0gZmFjdG9yKGtjbHVzdF9rOCRjbHVzdGVyKSkKYGBgCgojIyMgVmlzdWFsaXplIENsdXN0ZXIgQXNzaWdubWVudHMKCmBgYHtyfQojIFZpc3VhbGl6ZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBvbiB0aGUgb3JpZ2luYWwgc2NhdHRlcnBsb3QKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBCdWRnZXQsIHkgPSBSdW50aW1lLCBjb2xvciA9IGtjbHVzdF84KSkgKwogICAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMjIEludGVycHJldGluZyBDbHVzdGVycwoKIyMjIyBFeHBsb3JpbmcgR2VucmUgQnJlYWtkb3duCgpgYGB7cn0KCiMgQ291bnQgb2YgTW92aWVzIHBlciBHZW5yZSAoUHJpbWFyeSBHZW5yZSkKaW1kYl9jbGVhbiAlPiUKICBjb3VudChHZW5yZV8xKQoKIyBDb3VudCBvZiBNb3ZpZXMgcGVyIEdlbnJlIChTZWNvbmRhcnkgR2VucmUpCmltZGJfY2xlYW4gJT4lCiAgY291bnQoR2VucmVfMikKCiMgQ291bnQgb2YgTW92aWVzIHBlciBHZW5yZSAoT3ZlcmFsbCBHZW5yZSkKaW1kYl9jbGVhbiAlPiUKICBjb3VudChOZXdfR2VucmUpCmBgYAoKYGBge3J9CiMgR2VucmVzIHZzIENsdXN0ZXIKCiMgSG93IG1hbnkgb2YgZWFjaCBHZW5yZSAxIGluIGVhY2ggY2x1c3RlcgppbWRiX2NsZWFuICU+JQogIGdyb3VwX2J5KGtjbHVzdF84KSAlPiUKICBjb3VudChHZW5yZV8xKQoKIyBIb3cgbWFueSBvZiBlYWNoIEdlbnJlIDIgaW4gZWFjaCBjbHVzdGVyCmltZGJfY2xlYW4gJT4lCiAgZ3JvdXBfYnkoa2NsdXN0XzgpICU+JQogIGNvdW50KEdlbnJlXzIpCgojIEhvdyBtYW55IG1vdmllcyBpbiBlYWNoIGNsdXN0ZXIKaW1kYl9jbGVhbiU+JQogIGNvdW50KGtjbHVzdF84KQpgYGAKCiMjIyMgVmlzdWFsaXphdGlvbnMgb2YgR2VucmVzIGluIEVhY2ggQ2x1c3RlcgoKYGBge3J9CgojIEdlbnJlIDEKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBrY2x1c3RfOCwgZmlsbCA9IEdlbnJlXzEpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgbGFicyh4ID0gIkNsdXN0ZXIiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgojIEdlbnJlIDIKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBrY2x1c3RfOCwgZmlsbCA9IEdlbnJlXzIpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgbGFicyh4ID0gIkNsdXN0ZXIiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgojIE92ZXJhbGwgR2VucmUKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBrY2x1c3RfOCwgZmlsbCA9IE5ld19HZW5yZSkpICsKICAgIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgICBsYWJzKHggPSAiQ2x1c3RlciIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmBgYAoKIyMgSGllcmFyY2hpYWwgQ2x1c3RlcmluZwoKIyMjIFNldCB1cCBDbHVzdGVyaW5nIGFuZCBEaXN0YW5jZSBNYXRyaXgKCmBgYHtyfQojIFJhbmRvbSBzdWJzYW1wbGUgb2YgMjUgTW92aWVzCnNldC5zZWVkKDI1MykKCmltZGJfaGMgPC0gaW1kYl9jbGVhbiAlPiUKICAgIHNsaWNlX3NhbXBsZShuID0gMjUpCgojIFNlbGVjdCB0aGUgdmFyaWFibGVzIHRvIGJlIHVzZWQgaW4gY2x1c3RlcmluZwppbWRiX2hjX3N1YiA8LSBpbWRiX2hjICU+JQogICAgc2VsZWN0KEdyb3NzLCBCdWRnZXQpCgppbWRiX2hjX2Z1bGwgPC0gaW1kYl9jbGVhbiAlPiUKICBzZWxlY3QoR3Jvc3MsIEJ1ZGdldCkKCiMgU3VtbWFyeSBzdGF0aXN0aWNzIGZvciB0aGUgdmFyaWFibGVzCnN1bW1hcnkoaW1kYl9oY19zdWIpCgojIENvbXB1dGUgYSBkaXN0YW5jZSBtYXRyaXggb24gdGhlIHNjYWxlZCBkYXRhCmRpc3RfbWF0X3NjYWxlZCA8LSBkaXN0KHNjYWxlKGltZGJfaGNfc3ViKSkgICAgICMgU3Vic2V0IERpc3RhbmNlIE1hdHJpeAoKZGlzdF9tYXRfZnVsbCA8LSBkaXN0KHNjYWxlKGltZGJfaGNfZnVsbCkpICAgICAgIyBGdWxsIERhdGEgRGlzdGFuY2UgTWF0cml4CmBgYAoKIyMjIENyZWF0ZSBDbHVzdGVycwoKYGBge3J9CmltZGJfaGNfYXZnIDwtIGhjbHVzdChkaXN0X21hdF9zY2FsZWQsIG1ldGhvZCA9ICJhdmVyYWdlIikgICAgIyBTdWJzZXQKaW1kYl9mdWxsX2F2ZyA8LSBoY2x1c3QoZGlzdF9tYXRfZnVsbCwgbWV0aG9kID0gImF2ZXJhZ2UiKSAgICAjIEZ1bGwgRGF0YQoKYGBgCgojIyMgVmlzdWFsaXplIERlbmRyb2dyYW1zCgpgYGB7cn0KIyBQbG90IGRlbmRyb2dyYW0gb24gU3Vic2V0CnBsb3QoaW1kYl9oY19hdmcpCmBgYAoKYGBge3J9CiMgQWRkaW5nIEdlbnJlIExhYmVscwoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gaW1kYl9oYyRHZW5yZV8xKQoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gcGFzdGUoaW1kYl9oYyRHZW5yZV8xLCBpbWRiX2hjJEdlbnJlXzIpKQoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gcGFzdGUoaW1kYl9oYyRHZW5yZV8xLCBpbWRiX2hjJEdlbnJlXzIsIGltZGJfaGMkR2VucmVfMykpCgpwbG90KGltZGJfaGNfYXZnLCBsYWJlbHMgPSBwYXN0ZShpbWRiX2hjJE5ld19HZW5yZSkpCmBgYAoKIyMjIEN1dHRpbmcgdGhlIFRyZWUKCmBgYHtyfQppbWRiX2NsZWFuIDwtIGltZGJfY2xlYW4gJT4lCiAgICBtdXRhdGUoCiAgICAgICAgaGNsdXN0X251bSA9IGZhY3RvcihjdXRyZWUoaW1kYl9mdWxsX2F2ZywgayA9IDMpKSAjIEN1dCBpbnRvIDYgY2x1c3RlcnMgKGspCiAgICApCmBgYAoKIyMjIFZpc3VhbGl6aW5nIEdlbnJlcyBpbiBGaW5hbCBDbHVzdGVycyAoRnVsbCBEYXRhKQoKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IGhjbHVzdF9udW0sIGZpbGwgPSBHZW5yZV8xKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGxhYnMoeCA9ICJDbHVzdGVyIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW4sIGFlcyh4ID0gaGNsdXN0X251bSwgZmlsbCA9IE5ld19HZW5yZSkpICsKICAgIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgICBsYWJzKHggPSAiQ2x1c3RlciIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IEJ1ZGdldCwgeSA9IEdyb3NzLCBjb2xvciA9IGhjbHVzdF9udW0pKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoK